#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <string.h>

#include <errno.h> 
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>



static unsigned int HASH_RESULT_LEN = 32;
 
int base64_encode(char *in_str, int in_len, char *out_str)
{
    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;
    size_t size = 0;
 
    if (in_str == NULL || out_str == NULL)
        return -1;
 
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);
 
    BIO_write(bio, in_str, in_len);
    BIO_flush(bio);
 
    BIO_get_mem_ptr(bio, &bptr);
    memcpy(out_str, bptr->data, bptr->length);
    out_str[bptr->length] = '\0';
    size = bptr->length;
 
    BIO_free_all(bio);
    return size;
}
 
int base64_decode(char *in_str, int in_len, char *out_str)
{
    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;
    int counts;
    int size = 0;
 
    if (in_str == NULL || out_str == NULL)
        return -1;
 
    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
 
    bio = BIO_new_mem_buf(in_str, in_len);
    bio = BIO_push(b64, bio);
 
    size = BIO_read(bio, out_str, in_len);
    out_str[size] = '\0';
 
    BIO_free_all(bio);
    return size;
}

int gen_key(const char* pri_file, const char* pub_file)
{
    EC_KEY* eckey = EC_KEY_new_by_curve_name(NID_sm2);

    EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sm2);
    EC_KEY_set_group(eckey, group);

    BIO* param = BIO_new_file("/tmp/param.cache", "w");
    PEM_write_bio_ECPKParameters(param, group);

    EC_KEY_generate_key(eckey);

    BIO* prikey = BIO_new_file(pri_file, "w");
    BIO* pubkey = BIO_new_file(pub_file, "w");

    PEM_write_bio_ECPrivateKey(prikey, eckey, NULL, NULL, 0, NULL, NULL);
    PEM_write_bio_EC_PUBKEY(pubkey, eckey);

    BIO_free(param);
    BIO_free(prikey);
    BIO_free(pubkey);
    return 0;
}

EVP_PKEY* read_key_bio(const char* key_file, const int type)
{
    BIO* bio = BIO_new_file(key_file, "r");

    EVP_PKEY* key = EVP_PKEY_new();
    if (0 == type) {
        key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
    } else {
        key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
    }

    EVP_PKEY_set_alias_type(key, EVP_PKEY_SM2);
    BIO_free(bio);
    return key;
}

size_t do_encrypt(EVP_PKEY* key, unsigned char* out, const unsigned char* in,
                  size_t inlen)
{
    size_t ret        = 0;
    EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(key, NULL);
    EVP_PKEY_encrypt_init(ctx);
    EVP_PKEY_encrypt(ctx, out, &ret, in, inlen);
    EVP_PKEY_CTX_free(ctx);
    return ret;
}

size_t do_decrypt(EVP_PKEY* key, unsigned char* out, const unsigned char* in,
                  size_t inlen)
{
    size_t ret        = inlen;
    EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(key, NULL);
    EVP_PKEY_decrypt_init(ctx);
    EVP_PKEY_decrypt(ctx, out, &ret, in, inlen);
    EVP_PKEY_CTX_free(ctx);
    return ret;
}

unsigned int hash_str(const char* str, const size_t len,
                      unsigned char* hash_result)
{
    unsigned int ret;
    const EVP_MD* alg = EVP_sm3();
    EVP_Digest(str, len, hash_result, &ret, alg, NULL);
    return ret;
}

/**
 * @brief 加密或解密数据
 *
 * @param in 输入数据
 * @param inl 输入数据的长度
 * @param out 输出数据
 * @param do_encrypt 1-加密，0-解密
 * @return void 
 */
void do_crypt(const unsigned char* in, const unsigned int inlen,
             unsigned char* out, int do_encrypt, const unsigned char* key)
{
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    EVP_CipherInit_ex(ctx, EVP_sm4_cbc(), NULL, key, NULL, do_encrypt);
    EVP_Cipher(ctx, out, in, inlen);
    EVP_CIPHER_CTX_free(ctx);
    return;
}
 
int main()
{
    
    printf("\n");
    printf("\n");
    printf("BASE64 : \n");
    char instr[] = "hello";
    char outstr1[1024] = {0};
    base64_encode(instr,5,outstr1);
    printf("base64:%s\n",outstr1);
 
    char outstr2[1024] = {0};
    base64_decode(outstr1,strlen(outstr1),outstr2);
    printf("str:%s\n",outstr2);

    printf("\n");
    printf("\n");
    printf("SM2 en de : \n");
    size_t ret;

    const char* pub_file = "./pub_key.pem";
    const char* pri_file = "./pri_key.pem";
    // 生成公钥和私钥并写入文件中
    if (gen_key(pri_file, pub_file)) {
        printf("gen key failed.");
        exit(1);
    }
    // 读取公钥和私钥
    EVP_PKEY* pub_key = read_key_bio(pub_file, 0);
    EVP_PKEY* pri_key = read_key_bio(pri_file, 1);

    unsigned char data[]          = "20191331 lyx";
    unsigned char enc_txt[BUFSIZ] = {0};
    unsigned char dec_txt[BUFSIZ] = {0};
    printf("data= %s\n", data);

    // 公钥加密
    ret = do_encrypt(pub_key, enc_txt, data, strlen((const char*)data));
    printf("ret=%ld, enc= ", ret);
    for (size_t i = 0; i < ret; i++){
        printf("%2X", enc_txt[i]);
    }
    
    // 私钥解密
    ret = do_decrypt(pri_key, dec_txt, enc_txt, ret);
    dec_txt[ret] = 0;
    printf("\nret=%ld, dec= %s\n", ret, dec_txt);

    EVP_PKEY_free(pub_key);
    EVP_PKEY_free(pri_key);


    printf("\n");
    printf("\n");
    printf("SM3 : \n");
    char* str3 = "20191331 lyx";
    // HASH_RESULT_LEN = EVP_MD_size(EVP_sm3());
    unsigned char hash_result[HASH_RESULT_LEN];
    unsigned int retlen = hash_str(str3, strlen(str3), hash_result);

    printf("hash '%s', return len=%d\nhash val=", str3, retlen);
    for (int i = 0; i < HASH_RESULT_LEN; i++) {
        printf("%02x", hash_result[i]);
    }
    printf("\n");


    printf("\n");
    printf("\n");
    printf("SM4 : \n");
    unsigned char key[] = {0, 1, 2,  3,  4,  5,  6,  7,
                           8, 9, 10, 11, 12, 13, 14, 15};
    const char* str4     = "20191331 lyx";
    printf("origin data= %s\n", str4);

    // encrypt
    unsigned char buf[BUFSIZ] = {0};
    //printf("str4 len %d ",strlen(str4));
    do_crypt((const unsigned char*)str4, strlen(str4), buf, 1, key);
    printf("after encrypt str(hex)= ");
    for (int i = 0; i < strlen(str4); i++) {
        printf("%2X", buf[i]);
    }

    // decrypt
    unsigned char bufout[BUFSIZ] = {0};
    //printf("buf len %d ",strlen((const char*)buf));
    do_crypt(buf, strlen(str4), bufout, 0, key);
    printf("\nafter decrypt data= %s\n", bufout);
    return 0;   

}
